const YEAR = 'year';
const MONTH = 'month';
const DAY = 'day';
const HOUR = 'hour';
const MINUTE = 'minute';
const SECOND = 'second';
const MILLISECOND = 'millisecond';

export default {
  parse(date, date_separator = '-', time_separator = /[.:]/) {
    if (date instanceof Date) {
      return date;
    }
    if (typeof date === 'number') {
      if (String(date).length === 10) {
        return new Date(date * 1000);
      } else if (String(date).length === 13) {
        return new Date(date);
      }
      return null;
    }
    if (typeof date === 'string') {
      let date_parts, time_parts;
      const parts = date.split(' ');

      date_parts = parts[0]
        .split(date_separator)
        .map((val) => parseInt(val, 10));
      time_parts = parts[1] && parts[1].split(time_separator);

      // month is 0 indexed
      date_parts[1] = date_parts[1] - 1;

      let vals = date_parts;

      if (time_parts && time_parts.length) {
        if (time_parts.length == 4) {
          time_parts[3] = '0.' + time_parts[3];
          time_parts[3] = parseFloat(time_parts[3]) * 1000;
        }
        vals = vals.concat(time_parts);
      }

      return new Date(...vals);
    }
  },

  to_string(date, with_time = false) {
    if (!(date instanceof Date)) {
      throw new TypeError('Invalid argument type');
    }
    const vals = this.get_date_values(date).map((val, i) => {
      if (i === 1) {
        // add 1 for month
        val = val + 1;
      }

      if (i === 6) {
        return padStart(val + '', 3, '0');
      }

      return padStart(val + '', 2, '0');
    });
    const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
    const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;

    return date_string + (with_time ? ' ' + time_string : '');
  },

  format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
    const dateTimeFormat = new Intl.DateTimeFormat(lang, {
      month: 'long'
    });
    const month_name = dateTimeFormat.format(date);
    const month_name_capitalized =
            month_name.charAt(0).toUpperCase() + month_name.slice(1);

    const values = this.get_date_values(date).map(d => padStart(d, 2, 0));
    const format_map = {
      YYYY: values[0],
      MM: padStart(+values[1] + 1, 2, 0),
      DD: values[2],
      HH: values[3],
      mm: values[4],
      ss: values[5],
      SSS: values[6],
      D: values[2],
      MMMM: month_name_capitalized,
      MMM: month_name_capitalized
    };

    let str = format_string;
    const formatted_values = [];

    Object.keys(format_map)
      .sort((a, b) => b.length - a.length) // big string first
      .forEach((key) => {
        if (str.includes(key)) {
          str = str.replace(key, `$${formatted_values.length}`);
          formatted_values.push(format_map[key]);
        }
      });

    formatted_values.forEach((value, i) => {
      str = str.replace(`$${i}`, value);
    });

    return str;
  },

  diff(date_a, date_b, scale = DAY) {
    let milliseconds, seconds, hours, minutes, days, months, years;

    milliseconds = date_a - date_b;
    seconds = milliseconds / 1000;
    minutes = seconds / 60;
    hours = minutes / 60;
    days = hours / 24;
    months = days / 30;
    years = months / 12;

    if (!scale.endsWith('s')) {
      scale += 's';
    }

    return Math.floor(
      {
        milliseconds,
        seconds,
        minutes,
        hours,
        days,
        months,
        years
      }[scale]
    );
  },

  today() {
    const vals = this.get_date_values(new Date()).slice(0, 3);
    return new Date(...vals);
  },

  now() {
    return new Date();
  },

  add(date, qty, scale) {
    qty = parseInt(qty, 10);
    const vals = [
      date.getFullYear() + (scale === YEAR ? qty : 0),
      date.getMonth() + (scale === MONTH ? qty : 0),
      date.getDate() + (scale === DAY ? qty : 0),
      date.getHours() + (scale === HOUR ? qty : 0),
      date.getMinutes() + (scale === MINUTE ? qty : 0),
      date.getSeconds() + (scale === SECOND ? qty : 0),
      date.getMilliseconds() + (scale === MILLISECOND ? qty : 0)
    ];
    return new Date(...vals);
  },

  start_of(date, scale) {
    const scores = {
      [YEAR]: 6,
      [MONTH]: 5,
      [DAY]: 4,
      [HOUR]: 3,
      [MINUTE]: 2,
      [SECOND]: 1,
      [MILLISECOND]: 0
    };

    function should_reset(_scale) {
      const max_score = scores[scale];
      return scores[_scale] <= max_score;
    }

    const vals = [
      date.getFullYear(),
      should_reset(YEAR) ? 0 : date.getMonth(),
      should_reset(MONTH) ? 1 : date.getDate(),
      should_reset(DAY) ? 0 : date.getHours(),
      should_reset(HOUR) ? 0 : date.getMinutes(),
      should_reset(MINUTE) ? 0 : date.getSeconds(),
      should_reset(SECOND) ? 0 : date.getMilliseconds()
    ];

    return new Date(...vals);
  },

  clone(date) {
    return new Date(...this.get_date_values(date));
  },

  get_date_values(date) {
    return [
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      date.getHours(),
      date.getMinutes(),
      date.getSeconds(),
      date.getMilliseconds()
    ];
  },

  get_days_in_month(date) {
    const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    const month = date.getMonth();

    if (month !== 1) {
      return no_of_days[month];
    }

    // Feb
    const year = date.getFullYear();
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
      return 29;
    }
    return 28;
  }
};

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
function padStart(str, targetLength, padString) {
  str = str + '';
  targetLength = targetLength >> 0;
  padString = String(typeof padString !== 'undefined' ? padString : ' ');
  if (str.length > targetLength) {
    return String(str);
  } else {
    targetLength = targetLength - str.length;
    if (targetLength > padString.length) {
      padString += padString.repeat(targetLength / padString.length);
    }
    return padString.slice(0, targetLength) + String(str);
  }
}
